/**********************************************************************************************
*    File Name:  sdram_ctl.v  
*      Version:  1.0
*      Created:  2009-08-28
*       Author:  Xianyong Chen
*        Email:  cxybuaa@hotmail.com
*  Description:  This sdram controller is used to write and read SDRAM witch
                 column, row, bank, and data width can be revised by parameters. 

*  Rev   Author          Date         Changes
*  ----  -------------   ----------   ----------------------------------------------------
*  0.0   xychen          08/28/2009   -start
*  0.1   xychen          09/01/2009   -simulate successfully with ModelSim SE PLUS 6.1f
*  0.2   xychen          09/02/2009   -download to a PCB, read & write successfully on 150MHz
*  0.3   xychen          11/21/2013   -change to read & write on 200MHz
*  0.4   xychen          08/15/2014   -change to run on Altera FPGA(EP4CE6) with 200MHz
**********************************************************************************************/
`timescale 1ns/100ps

module sdram_ctl
        #(
        parameter       ROW_WTH                 = 11,           //2^11 = 2048 rows
        parameter       COL_WTH                 = 8,            //2^8 = 256 columns
        parameter       BANK_WTH                = 2,            //2^2 = 4 banks
        parameter       DATA_WTH                = 32,           //data width
        parameter       DQM_WTH                 = DATA_WTH/8,   //one DQM manage one byte
        parameter       ADDR_WTH                = COL_WTH + BANK_WTH + ROW_WTH
        )
        (
        //system signals
        input   wire                            sclk,
        input   wire                            sclk_shift,
        input   wire                            rst_n,
        //interface with internal logic
        output  reg                             sdram_initializing,
        input   wire                            wr_en,
        input   wire                            rd_en,
        output  reg                             wr_rd_end,
        input   wire    [ADDR_WTH-1:0]          start_addr,
        input   wire    [ADDR_WTH-1:0]          length,
        output  reg                             ask_for_data,
        input   wire    [DATA_WTH-1:0]          data_in,
        output  reg     [DATA_WTH-1:0]          data_out,
        output  reg                             data_out_valid,
        // interface with SDRAM
        output  wire                            sdram_cke,
        output  wire                            sdram_cs_n,
        output  reg                             sdram_ras_n,
        output  reg                             sdram_cas_n,
        output  reg                             sdram_we_n,
        output  wire                            sdram_clk,
        output  reg     [BANK_WTH-1:0]          sdram_bank,
        output  reg     [ROW_WTH-1:0]           sdram_addr,
        output  wire    [DQM_WTH-1:0]           sdram_dqm,
        inout   tri     [DATA_WTH-1:0]          sdram_data
                  );
//********************************************************
//      Parameter Declaration
localparam       MAX_AUTO_REF_GAP_CNT    = 'd3125;       //200MHz
localparam       MAX_INIT_CNT            = 'hB000;
localparam       MAX_ROW_ACTIVE_CNT      = 'd2;
localparam       MAX_PRECHARGE_ALL_CNT   = 'd6;
localparam       MAX_AUTO_REF_CNT        = 'd14;
//********************************************************
// command for DDR
// {CKE,/CS,/RAS,/CAS,/WE}
localparam       CMD_MRS                 = 5'b10000;
localparam       CMD_AUTO_REF            = 5'b10001;
localparam       CMD_ROW_ACT             = 5'b10011;
localparam       CMD_PRECHARGE_ALL       = 5'b10010;
localparam       CMD_WR                  = 5'b10100;
localparam       CMD_RD                  = 5'b10101;
localparam       CMD_NOP                 = 5'b10111;

localparam       Latency_2               = 3'b010;
localparam       Latency_3               = 3'b011;
localparam       Sequential              = 1'b0;
localparam       Interleave              = 1'b1;
localparam       Burst_1                 = 3'b000;
localparam       Burst_2                 = 3'b001;
localparam       Burst_4                 = 3'b010;
localparam       Burst_8                 = 3'b011;
//********************************************************
//state localparam
reg     [2:0]   state;
localparam       IDLE                    = 'd0;
localparam       ROW_ACT                 = 'd1;
localparam       BURST_WR                = 'd2;
localparam       BURST_RD                = 'd3;
localparam       PRECHARGE_ALL           = 'd4;
localparam       AUTO_REF                = 'd5;
//********************************************************
//      Internal Registers
reg     [15:0]                  init_cnt;
reg                             init_cmd_en;
reg                             init_MRS_en;
reg                             init_PA_en;
reg                             init_AR_en;

reg     [1:0]                   row_act_cnt;
reg                             row_act_end;
reg     [ADDR_WTH-1:0]          burst_cnt;
reg     [2:0]                   precharge_all_cnt;
reg                             precharge_all_end;
reg     [13:0]                  auto_ref_gap_cnt;
reg                             auto_ref_en;
reg     [3:0]                   auto_ref_cnt;
reg                             auto_ref_end;

reg     [ADDR_WTH-1:0]          cur_addr;
reg     [ADDR_WTH-1:0]          cur_addr_tmp;

(* KEEP = "TRUE", IOB = "FORCE" *) reg     [DATA_WTH-1:0]          sdram_data_out;
reg                             sdram_data_out_en;
reg                             read_pulse;
reg     [7:0]                   read_pulse_tmp;

(* KEEP = "TRUE", IOB = "FORCE" *) reg     [DATA_WTH-1:0]          sdram_dq_in;
reg     [ADDR_WTH-1:0]          length_tmp;
reg     [ADDR_WTH-1:0]          length_minus_1;

reg                             burst_wr_en;
reg                             row_act_en;
reg                             precharge_all_en;
reg                             burst_rd_en;
reg                             auto_refresh_en;
//**************************************************************************************
//init_cnt[15:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                init_cnt <= 'b0;
        else if(sdram_initializing==1)
                init_cnt <= init_cnt + 1'b1;

//sdram_initializing
//always@(posedge sclk or negedge rst_n)
//        if(!rst_n)
//                sdram_initializing <= 1'b1;
//        else if (init_cnt[15:12]==MAX_INIT_CNT[15:12])
//                sdram_initializing <= 1'b0;

//sdram_initializing
always@(posedge sclk)
        if(init_cnt[15:12]==MAX_INIT_CNT[15:12])
                sdram_initializing <= 'b0;
        else
                sdram_initializing <= 1'b1;

//init_cmd_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                init_cmd_en <= 1'b0;
        else if(init_cnt[15:9]==7'h50 && init_cnt[3:0]==4'h0)
                init_cmd_en <= 1'b1;
        else
                init_cmd_en <= 1'b0;

//init_MRS_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                init_MRS_en <= 1'b0;
        else if(init_cmd_en==1 && init_cnt[8:4]==5'd8)
                init_MRS_en <= 1'b1;
        else
                init_MRS_en <= 1'b0;

//init_PA_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                init_PA_en <= 1'b0;
        else if(init_cmd_en==1 && (init_cnt[8:4]==5'd0 || init_cnt[8:4]==5'd9))
                init_PA_en <= 1'b1;
        else
                init_PA_en <= 1'b0;

//init_AR_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                init_AR_en <= 1'b0;
        else if(init_cmd_en==1 && (init_cnt[8:4]>=5'd1 && init_cnt[8:4]<=5'd7))
                init_AR_en <= 1'b1;
        else
                init_AR_en <= 1'b0;
//**************************************************************************************
//length_tmp[ADDR_WTH-1:0]
always@(posedge sclk)
        if(state==IDLE && (wr_en==1 || rd_en==1))
                length_tmp <= length;

//length_minus_1[ADDR_WTH-1:0]
// length_minus_1 is used to improve performance
always@(posedge sclk)
        length_minus_1 <= length_tmp - 1'b1;

//wr_rd_end
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                wr_rd_end <= 1'b0;
        else if(state==PRECHARGE_ALL && precharge_all_cnt==MAX_PRECHARGE_ALL_CNT-2 && burst_cnt==length_tmp)
                wr_rd_end <= 1'b1;
        else
                wr_rd_end <= 1'b0;
//***************************************************************************************               
//state[2:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                state <= IDLE;
        else
        case(state)
                IDLE:             
                        if(sdram_initializing==0)
                        begin
                                //should not use edge of wr_en or rd_en,
                                //for auto_ref may miss one edge
                                if(wr_en==1 || rd_en==1)
                                        state <= ROW_ACT;
                                else if(auto_ref_en==1)
                                        state <= AUTO_REF;
                        end
                                        
                ROW_ACT:       
                        if(row_act_end==1)
                        begin
                                if(wr_en==1)
                                        state <= BURST_WR;
                                else if(rd_en==1)
                                        state <= BURST_RD;
                        end

                BURST_WR:
                        //change row
                        if(burst_cnt==length_minus_1 || (&cur_addr[BANK_WTH+COL_WTH-1:0])==1)
                                state <= PRECHARGE_ALL;
                        //change bank
                        else if((&cur_addr[COL_WTH-1:0])==1)
                                state <= ROW_ACT;

                BURST_RD:
                        //change row
                        if(burst_cnt==length_minus_1 || (&cur_addr[BANK_WTH+COL_WTH-1:0])==1)
                                state <= PRECHARGE_ALL;
                        //change bank
                        else if((&cur_addr[COL_WTH-1:0])==1) 
                                state <= ROW_ACT;
                                                
                PRECHARGE_ALL:    
                        if(precharge_all_end==1)
                        begin
                                if(burst_cnt<length_tmp)
                                        state <= ROW_ACT;
                                else
                                        state <= IDLE;
                        end
                //------------------------------------------------------
                AUTO_REF:
                        if(auto_ref_end==1)
                                state <= IDLE;
                
                default:        state <= IDLE;
        endcase
//***************************************************************************************
//row_act_cnt[1:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                row_act_cnt <= 'b0;
        else if(state==ROW_ACT)
                row_act_cnt <= row_act_cnt + 1'b1;
        else
                row_act_cnt <= 'b0;

//row_act_end
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                row_act_end <= 'b0;
        else if(row_act_cnt==MAX_ROW_ACTIVE_CNT)
                row_act_end <= 1'b1;
        else
                row_act_end <= 'b0;

//burst_cnt[ADDR_WTH-1:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                burst_cnt <= 'b0;
        else
        case(state)
                IDLE:           burst_cnt <= 'b0;
                BURST_WR,
                BURST_RD:       burst_cnt <= burst_cnt + 1'b1;
        endcase

//precharge_all_cnt[2:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                precharge_all_cnt <= 2'b0;
        else if(state==PRECHARGE_ALL)
                precharge_all_cnt <= precharge_all_cnt + 1'b1;
        else 
                precharge_all_cnt <= 2'b0;
                
//precharge_all_end
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                precharge_all_end <= 1'b0;
        else if(precharge_all_cnt==MAX_PRECHARGE_ALL_CNT)
                precharge_all_end <= 1'b1;
        else 
                precharge_all_end <= 1'b0;
//******************************************************************************************
//burst_wr_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                burst_wr_en <= 'b0;
        else if(state==BURST_WR)
                burst_wr_en <= 1'b1;
        else
                burst_wr_en <= 'b0;

//row_act_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                row_act_en <= 'b0;
        else if(state==ROW_ACT && row_act_cnt==0)
                row_act_en <= 1'b1;
        else
                row_act_en <= 'b0;

//precharge_all_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                precharge_all_en <= 'b0;
        else if(state==PRECHARGE_ALL && precharge_all_cnt==MAX_PRECHARGE_ALL_CNT-2)
                precharge_all_en <= 1'b1;
        else
                precharge_all_en <= 'b0;

//burst_rd_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                burst_rd_en <= 'b0;
        else if(row_act_end==1 && rd_en==1)
                burst_rd_en <= 1'b1;
        else if(burst_cnt==length_minus_1 || (&cur_addr[BANK_WTH+COL_WTH-1:0])==1
                || (&cur_addr[COL_WTH-1:0])==1)
                burst_rd_en <= 'b0;

//auto_refresh_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                auto_refresh_en <= 'b0;
        else if(state==AUTO_REF && auto_ref_cnt==0)
                auto_refresh_en <= 1'b1;
        else
                auto_refresh_en <= 'b0;
//******************************************************************************************
//auto_ref_gap_cnt[13:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                auto_ref_gap_cnt <= 'b0;
        else if(sdram_initializing==0)
        begin
                if(auto_ref_gap_cnt==MAX_AUTO_REF_GAP_CNT)
                        auto_ref_gap_cnt <= 'b0;
                else
                        auto_ref_gap_cnt <= auto_ref_gap_cnt + 1'b1;
        end

//auto_ref_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                auto_ref_en <= 'b0;
        else if(auto_ref_gap_cnt==MAX_AUTO_REF_GAP_CNT)
                auto_ref_en <= 1'b1;
        else
                auto_ref_en <= 'b0;
                
//auto_ref_cnt[3:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                auto_ref_cnt <= 'b0;
        else if(state==AUTO_REF)
                auto_ref_cnt <= auto_ref_cnt + 1'b1;
        else
                auto_ref_cnt <= 'b0;
                
//auto_ref_end
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                auto_ref_end <= 'b0;
        else if(auto_ref_cnt==MAX_AUTO_REF_CNT)
                auto_ref_end <= 1'b1;
        else
                auto_ref_end <= 'b0;

//cur_addr[ADDR_WTH-1:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                cur_addr <= 'b0;
        else if(state==IDLE && (wr_en==1 || rd_en==1))
                cur_addr <= start_addr;
        else if(state==BURST_WR || state==BURST_RD)
                cur_addr <= cur_addr + 1'd1;

//cur_addr_tmp[ADDR_WTH-1:0]
always@(posedge sclk)
        cur_addr_tmp <= cur_addr;
//******************************************************************************************
//reg     [4:0]                   sdram_cmd;
////sdram_cmd[4:0] {CKE, /CS, /RAS, /CAS, /WE}
//always@(posedge sclk or negedge rst_n)
//        if(!rst_n)
//                sdram_cmd <= CMD_NOP;
//        else if(init_MRS_en==1)
//                sdram_cmd <= CMD_MRS;
//        else if(state==ROW_ACT && row_act_cnt==1)
//                sdram_cmd <= CMD_ROW_ACT;
//        else if(burst_wr_en==1)
//                sdram_cmd <= CMD_WR;
//        else if(state==BURST_RD)
//                sdram_cmd <= CMD_RD;
//        else if(init_PA_en==1 || (state==PRECHARGE_ALL && precharge_all_cnt==MAX_PRECHARGE_ALL_CNT-1))
//                sdram_cmd <= CMD_PRECHARGE_ALL;
//        else if(init_AR_en==1 || (state==AUTO_REF && auto_ref_cnt==1))
//                sdram_cmd <= CMD_AUTO_REF;
//        else
//                sdram_cmd <= CMD_NOP;
//
//assign {sdram_cke,sdram_cs_n,sdram_ras_n,sdram_cas_n,sdram_we_n} = sdram_cmd;

assign sdram_cke  = 1'b1;
assign sdram_cs_n = 1'b0;

//sdram_ras_n
//always@(posedge sclk)
//        if(init_MRS_en==1 || init_PA_en==1 || init_AR_en==1
//       || (state==ROW_ACT && row_act_cnt==1)
//       || (state==PRECHARGE_ALL && precharge_all_cnt==MAX_PRECHARGE_ALL_CNT-1)
//       || (state==AUTO_REF && auto_ref_cnt==1))
//                sdram_ras_n <= 'b0;
//        else
//                sdram_ras_n <= 1'b1;

//sdram_ras_n
always@(posedge sclk)
        if(init_MRS_en==1 || init_PA_en==1 || init_AR_en==1
           || row_act_en==1 || precharge_all_en==1 || auto_refresh_en==1)
                sdram_ras_n <= 'b0;
        else
                sdram_ras_n <= 1'b1;

//sdram_cas_n
always@(posedge sclk)
        if(init_MRS_en==1 || init_AR_en==1 || burst_wr_en==1 || burst_rd_en==1 || auto_refresh_en==1)
                sdram_cas_n <= 'b0;
        else
                sdram_cas_n <= 1'b1;

//sdram_we_n
always@(posedge sclk)
        if(init_MRS_en==1 || init_PA_en==1 || burst_wr_en==1 || precharge_all_en==1)
                sdram_we_n <= 'b0;
        else
                sdram_we_n <= 1'b1;
//******************************************************************************************
//sdram_bank[BANK_WTH-1:0]
always@(posedge sclk)
        if(init_MRS_en==1)
                sdram_bank <= 'b0;
        else if(burst_wr_en==1)
                sdram_bank <= cur_addr_tmp[BANK_WTH+COL_WTH:COL_WTH];
        else
                sdram_bank <= cur_addr[BANK_WTH+COL_WTH:COL_WTH];

//sdram_addr[ROW_WTH-1:0]
always@(posedge sclk)
        case({init_MRS_en,init_PA_en,burst_wr_en,row_act_en,burst_rd_en,precharge_all_en})
                6'b100000:      sdram_addr <= {6'b0,Latency_3,Sequential,Burst_1};
                6'b001000:      sdram_addr <= cur_addr_tmp[COL_WTH-1:0];
                6'b000100:      sdram_addr <= cur_addr[ADDR_WTH-1:BANK_WTH+COL_WTH];
                6'b000010:      sdram_addr <= cur_addr[COL_WTH-1:0];
                6'b010000,
                6'b000001:      sdram_addr <= 'd1024;
                default:        sdram_addr <= 'd0;
        endcase

//sdram_dqm[DQM_WTH-1:0]
assign sdram_dqm = 'b0;
//************************************************************************************************
//sdram_data_out[DATA_WTH-1:0]
//sdram_data_out_en
always @(posedge sclk)
    sdram_data_out <= data_in;

always@(posedge sclk)
    sdram_data_out_en <= burst_wr_en;

//sdram_data[DATA_WTH-1:0]
assign sdram_data = (sdram_data_out_en) ? sdram_data_out : {DATA_WTH{1'bz}};

//ask_for_data
always@(posedge sclk)
        if(row_act_end==1 && wr_en==1)
                ask_for_data <= 1'b1; 
        else if(state==BURST_WR && burst_cnt!=length_minus_1
                && (&cur_addr[BANK_WTH+COL_WTH-1:0])==0
                && (&cur_addr[COL_WTH-1:0])==0)
                ask_for_data <= 1'b1; 
        else
                ask_for_data <= 'b0;
//***********************************************************************************************
//read_pulse
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                read_pulse <= 1'b0;
        else if(burst_rd_en==1)
                read_pulse <= 1'b1;
        else
                read_pulse <= 1'b0;

//read_pulse_tmp[7:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                read_pulse_tmp <= 8'b0;
        else 
                read_pulse_tmp <= {read_pulse_tmp[6:0],read_pulse};

// rst_n may cause timing problem here, so no rst_n here
//sdram_dq_in[DATA_WTH-1:0]
always@(posedge sclk)
        sdram_dq_in <= sdram_data;

//data_out_valid
//data_out[DATA_WTH-1:0]
always@(posedge sclk)
    if(read_pulse_tmp[3]==1)          //could be changed
        begin
        data_out_valid <= 1'b1;
        data_out       <= sdram_dq_in;
        end
    else
        data_out_valid <= 'b0;

//sdram_clk
// Altera usage
assign sdram_clk = ~sclk;
// Xilinx usage
// sclk is a global clock, but sdram_clk pin is not
// a global clock pin, so direct assign can't be mapped.
//------------------------------------------
// instance of ODDR2
//------------------------------------------
//ODDR2
//        #(
//        .DDR_ALIGNMENT  ("NONE"         ),      // sets output alignment to "NONE", "C0" or "C1"
//        .INIT           (1'b0           ),      // set initial state of the Q output to 1'b0 or 1'b1
//        .SRTYPE         ("SYNC"         )       // specifies "SYNC" or "ASYNC" set/reset
//        )
//    ODDR2_inst(
//        .Q              (sdram_clk      ),      // 1-bit DDR output data
//        .CE             (1'b1           ),      // 1-bit clock input
//        .C0             (sclk_shift    ),      // 1-bit clock input
//        .C1             (~sclk_shift    ),      // 1-bit clock enable input
//        .D0             (1'b1           ),      // 1-bit data input (associated with C0)
//        .D1             (1'b0           ),      // 1-bit data input (associated with C1)
//        .R              (1'b0           ),      // 1-bit reset input
//        .S              (1'b0           )       // 1-bit set input
//        );
//******************************************************************************
endmodule
